#include "util.h"
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <mpi.h>

void broadcastInfo(info *sistema) {
  int i;

  MPI_Bcast(&sistema->order,1,MPI_INT,0,MPI_COMM_WORLD);
  MPI_Bcast(&sistema->test_row,1,MPI_INT,0,MPI_COMM_WORLD);
  MPI_Bcast(&sistema->MAX_ITER,1,MPI_INT,0,MPI_COMM_WORLD);
  MPI_Bcast(&sistema->erro,1,MPI_DOUBLE,0,MPI_COMM_WORLD);
  MPI_Bcast(sistema->seekTable_B,sistema->order,MPI_LONG,0,MPI_COMM_WORLD);

  /*
  for(i=0;i<sistema->order;i++) {
    MPI_Bcast(sistema->seekTable_A[i],sistema->order,MPI_LONG,0,MPI_COMM_WORLD);
  }*/
}

info * receiveInfo(char *file) {
  int i;
  info *sistema = (info *)malloc(sizeof(info));

  if(sistema == NULL)
    return NULL;

  sistema->f = (char *)malloc((strlen(file)+1)*sizeof(char));
  strcpy(sistema->f,file);

  MPI_Bcast(&sistema->order,1,MPI_INT,0,MPI_COMM_WORLD);
  MPI_Bcast(&sistema->test_row,1,MPI_INT,0,MPI_COMM_WORLD);
  MPI_Bcast(&sistema->MAX_ITER,1,MPI_INT,0,MPI_COMM_WORLD);
  MPI_Bcast(&sistema->erro,1,MPI_DOUBLE,0,MPI_COMM_WORLD);

  sistema->seekTable_B = (long *)malloc(sistema->order*sizeof(long));
  /*sistema->seekTable_A = (long **)malloc(sistema->order*sizeof(long *));
  for(i=0;i<sistema->order;i++) {
    sistema->seekTable_A[i] = (long *)malloc(sistema->order*sizeof(long));
  }*/

  MPI_Bcast(sistema->seekTable_B,sistema->order,MPI_LONG,0,MPI_COMM_WORLD);
  /*for(i=0;i<sistema->order;i++) {
    MPI_Bcast(sistema->seekTable_A[i],sistema->order,MPI_LONG,0,MPI_COMM_WORLD);
  }*/

  return sistema;
  
}

double calculateLine(double **A, double *X, int row, int order) {
  double result = 0.0;
  int i;

  for(i=0;i<order;i++) {
    result += A[row][i]*X[i];
  }

  return result;

}
info * createInfo(char *file) {
  info *inf = (info *)malloc(sizeof(info));
  FILE *f = fopen(file,"r");
  short int acabou = 0;
  unsigned i=0, j=0;

  if(f == NULL || inf == NULL)
    return NULL;

  /*
     Preenche a estrutura info com informações sobre o problema
  */
  inf->f = (char *)malloc((strlen(file)+1)*sizeof(char));
  strcpy(inf->f,file);
  fscanf(f,"%d\n",&(inf->order));
  fscanf(f,"%d\n",&(inf->test_row));
  fscanf(f,"%lf\n",&(inf->erro));
  fscanf(f,"%d\n",&(inf->MAX_ITER));

  /* 
     Aloca tabelas de deslocamento para as matrizes A e B
  */
  /*inf->seekTable_A = (long **)malloc(inf->order*sizeof(long *));

  for(i=0;i<inf->order;i++) {
    inf->seekTable_A[i] = (long *)malloc(inf->order*sizeof(long));
  }*/

  /* Para efeitos de simplicidade, a matriz B é armazenada como B transposta */
  inf->seekTable_B = (long *)malloc(inf->order*sizeof(long));

  /* Constroi a tabela de seeks de A */
  i = j = 0;
  do {
    char c;
    /* Pega o offset atual do arquivo */
    //inf->seekTable_A[i][j] = ftell(f);

    /* Anda no arquivo até achar o fim do número ou o fim da linha */
    do {
      c = fgetc(f);
    } while(c != '\n' && c != ' ');

    if(c == '\n') { /* Fim da linha na matriz */
      i++;
      j = 0;
    } else if(c == ' ') { /* Próximo número na linha */
      j++;
    }

    if(i == inf->order)
      acabou = 1;

  } while(!acabou);

  /* Constroi a tabela de seeks de B */
  acabou = 0;
  i = 0;
  do {
    char c;
    /* Pega o offset atual do arquivo */
    inf->seekTable_B[i] = ftell(f);

    /* Anda no arquivo até achar o fim do número */
    do {
      c = fgetc(f);
    } while(c != '\n');
    i++;

    if(i == inf->order)
      acabou = 1;

  } while(!acabou);

  fclose(f);

  return inf;

}

/* Para i = -1, retorna o j-ésimo elemento de B */
double getValue(info *inf, int i, int j) {
  FILE *f = fopen(inf->f,"r");
  double value;
  if(i >= inf->order || j >= inf->order || f == NULL)
    return 0.0;

  if(i != -1)
    fseek(f,inf->seekTable_A[i][j],SEEK_SET);
  else 
    fseek(f,inf->seekTable_B[j],SEEK_SET);

  fscanf(f,"%lf",&value);

  fclose(f);

  return value;
}

/* Retorna a coluna transposta */
double * loadColumn(info *inf, int j) {
  double *column = (double *)malloc(inf->order*sizeof(double));
  int i;

  for(i=0;i<inf->order;i++) {
    column[i] = getValue(inf,i,j);
  }

  return column;
}

int parada(double erro, double *X, double *Xk, int size) {
  int i;
  double max1 = fabs(Xk[0] - X[0]); 
  double max2 = fabs(Xk[0]);
  double aux;
  double current_error;

  for(i=1;i<size;i++) {
    aux = fabs(Xk[i] - X[i]);
    if(aux > max1) {
      max1 = aux;
    }
  }

  for(i=1;i<size;i++) {
    aux = fabs(Xk[i]);
    if(aux > max2) {
      max2 = aux;
    }
  }

  current_error = max1/max2;



  if(current_error < erro)
    return 1;
  else
    return 0;
}

void swap(double *X, double *Xk, int size) {
  int i;

  for(i=0;i<size;i++)
    X[i] = Xk[i];
}

double * getLine(info *sistema, int line) {
  int j;
  double *A = (double *)malloc(sistema->order*sizeof(double));
  FILE *f = fopen(sistema->f,"r");
  int new_line_count = 0;

  if(f == NULL || A == NULL)
    return NULL;

  while(new_line_count != line+4) {
    char c;
    while((c = fgetc(f)) != '\n');
    new_line_count++;
  }

  for(j=0;j<sistema->order;j++) {
    if(fscanf(f,"%lf",&A[j]) != 1)
      return NULL;
  }

  fclose(f);

  return A;
}

double calculateLineDistributed(info *sistema, double *X) {
  double *A = getLine(sistema,sistema->test_row);
  int i;
  double value=0.0;

  for(i=0;i<sistema->order;i++) {
    value += A[i]*X[i];
  }

  return value;
}

double ** getMatrixA(info *sistema) {
  double **A;
  int i,j;

  A = (double **)malloc(sistema->order*sizeof(double *));

  if(A == NULL)
    return NULL;

  for(i=0;i<sistema->order;i++) {
    A[i] = (double *)malloc(sistema->order*sizeof(double));
    if(A[i] == NULL)
      return NULL;
  }

  for(i=0;i<sistema->order;i++) {
    A[i] = getLine(sistema,i);
    /*for(j=0;j<sistema->order;j++) {
      A[i][j] = getValue(sistema,i,j);
    }*/
  }

  return A;
}

double *getMatrixB(info *sistema) {
  double *B = (double *)malloc(sistema->order*sizeof(double));
  int i;

  for(i=0;i<sistema->order;i++) {
    B[i] = getValue(sistema,-1,i);
  }

  return B;
}
